package cn.gzten.lambda.runtime;

import cn.gzten.lambda.runtime.api.RuntimeInvocationHeaders;
import cn.gzten.lambda.runtime.api.LambdaRuntimeHttpClient;
import cn.gzten.lambda.runtime.exception.InitializationException;
import cn.gzten.lambda.runtime.http.HttpResponse;
import cn.gzten.lambda.runtime.internal.HandlerRegistrationResolver;
import cn.gzten.lambda.runtime.internal.RequestHandlerInvoker;
import cn.gzten.lambda.runtime.logging.LambdaLoggerHolder;
import cn.gzten.lambda.runtime.util.EnvironmentVariableResolver;
import cn.gzten.lambda.runtime.util.ReservedEnvironmentVariables;
import cn.gzten.lambda.runtime.validation.RequestHandlerRegistrationValidator;

import java.time.LocalDateTime;
import java.util.Collection;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;

public class LambdaRuntime {
    private final Collection<RequestHandlerRegistration<?, ?>> handlerRegistrations;
    private final RequestHandlerRegistrationValidator registrationValidator;
    private final HandlerRegistrationResolver handlerRegistrationResolver;
//    private final ExecutorService executorService = Executors.newFixedThreadPool(10);

    public LambdaRuntime(Collection<RequestHandlerRegistration<?, ?>> handlerRegistrations) {
        this(handlerRegistrations, new RequestHandlerRegistrationValidator(), new HandlerRegistrationResolver());
    }

    public LambdaRuntime(Collection<RequestHandlerRegistration<?, ?>> handlerRegistrations, RequestHandlerRegistrationValidator registrationValidator,
                         HandlerRegistrationResolver handlerRegistrationResolver) {
        this.handlerRegistrations = handlerRegistrations;
        this.registrationValidator = registrationValidator;
        this.handlerRegistrationResolver = handlerRegistrationResolver;
    }

    public void run() {
        String runtimeApi = EnvironmentVariableResolver.resolve(ReservedEnvironmentVariables.AWS_LAMBDA_RUNTIME_API);
        LambdaRuntimeHttpClient runtimeHttpClient = new LambdaRuntimeHttpClient(runtimeApi);
        try {
            String handlerConfigurationValue = EnvironmentVariableResolver.resolve(ReservedEnvironmentVariables.HANDLER);
            RequestHandlerRegistration<?, ?> handlerRegistration = handlerRegistrationResolver.resolve(handlerConfigurationValue, handlerRegistrations);
            registrationValidator.validate(handlerRegistration);
            var counter = new AtomicLong(0);
            while (true) {
                if (LambdaLoggerHolder.isDebug()) {
                    LambdaLoggerHolder.getLambdaLogger().log("Before get an event: " + LocalDateTime.now());
                }
                /**
                 * This API will block if there is no new events
                 */
                HttpResponse event = runtimeHttpClient.nextInvocation();
                if (LambdaLoggerHolder.isDebug()) {
                    LambdaLoggerHolder.getLambdaLogger().log("Got " + counter.incrementAndGet() + "th events at " + LocalDateTime.now());
                }

                /**
                 * Once got an event, process it asynchronously. CompletableFuture is too fast, may get an issue,
                 * now use threading feature to slow it down.
                 *   CompletableFuture.supplyAsync(() -> {
                 *       return event.getHeaders().getSingleValueHeader(RuntimeInvocationHeaders.LAMBDA_RUNTIME_AWS_REQUEST_ID);
                 *   }).thenAccept(requestId -> {
                 *       try {
                 *           String response = RequestHandlerInvoker.invokeHandler(event, handlerRegistration);
                 *           runtimeHttpClient.invocationResponse(requestId, response);
                 *       } catch (Exception e) {
                 *           runtimeHttpClient.invocationError(requestId);
                 *           e.printStackTrace();
                 *       }
                 *   });
                 */
                /**
                issueTask(executorService, () -> {
                    var requestId = event.getHeaders().getSingleValueHeader(RuntimeInvocationHeaders.LAMBDA_RUNTIME_AWS_REQUEST_ID);
                    try {
                        String response = RequestHandlerInvoker.invokeHandler(event, handlerRegistration);
                        runtimeHttpClient.invocationResponse(requestId, response);
                    } catch (Exception e) {
                        runtimeHttpClient.invocationError(requestId);
                        e.printStackTrace();
                    }
                });*/

                /**
                 * Found that for this small memory mode, single thread would be much faster than multi-threading mode.
                 */
                var requestId = event.getHeaders().getSingleValueHeader(RuntimeInvocationHeaders.LAMBDA_RUNTIME_AWS_REQUEST_ID);
                try {
                    String response = RequestHandlerInvoker.invokeHandler(event, handlerRegistration);
                    runtimeHttpClient.invocationResponse(requestId, response);
                } catch (Exception e) {
                    runtimeHttpClient.invocationError(requestId);
                    e.printStackTrace();
                }
            }
        } catch (InitializationException e) {
            e.printStackTrace();
            runtimeHttpClient.initializationError(e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            runtimeHttpClient.initializationError("Initialization Error");
        }
    }

    public static final void issueTask(final ExecutorService es, final Runnable r) {
        boolean submitted = false;
        while(!submitted)
            try {
                es.submit(r);
                submitted = true;
            } catch (RejectedExecutionException e) {
                try {
                    Thread.sleep(5L);
                } catch (InterruptedException ex) {
                    System.out.println("Got interrupted exception: " + ex.getMessage());
                    throw new RuntimeException(ex);
                }
            }
    }
}
